//C- -------------------------------------------------------------------
//C- Java DjVu (r) (v. 0.8)
//C- Copyright (c) 2004-2005 LizardTech, Inc.  All Rights Reserved.
//C- Java DjVu is protected by U.S. Pat. No.C- 6,058,214 and patents
//C- pending.
//C-
//C- This software is subject to, and may be distributed under, the
//C- GNU General Public License, Version 2. The license should have
//C- accompanied the software or you may obtain a copy of the license
//C- from the Free Software Foundation at http://www.fsf.org .
//C-
//C- The computer code originally released by LizardTech under this
//C- license and unmodified by other parties is deemed "the LIZARDTECH
//C- ORIGINAL CODE."  Subject to any third party intellectual property
//C- claims, LizardTech grants recipient a worldwide, royalty-free,
//C- non-exclusive license to make, use, sell, or otherwise dispose of
//C- the LIZARDTECH ORIGINAL CODE or of programs derived from the
//C- LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU
//C- General Public License.   This grant only confers the right to
//C- infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to
//C- the extent such infringement is reasonably necessary to enable
//C- recipient to make, have made, practice, sell, or otherwise dispose
//C- of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to
//C- any greater extent that may be necessary to utilize further
//C- modifications or combinations.
//C-
//C- The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
//C- OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
//C- TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
//C- MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
//C-
//C- In addition, as a special exception, LizardTech Inc. gives permission
//C- to link the code of this program with the proprietary Java
//C- implementation provided by Sun (or other vendors as well), and
//C- distribute linked combinations including the two. You must obey the
//C- GNU General Public License in all respects for all of the code used
//C- other than the proprietary Java implementation. If you modify this
//C- file, you may extend this exception to your version of the file, but
//C- you are not obligated to do so. If you do not wish to do so, delete
//C- this exception statement from your version.
//C- -------------------------------------------------------------------
//C- Developed by Bill C. Riemers, Foxtrot Technologies Inc. as work for
//C- hire under US copyright laws.
//C- -------------------------------------------------------------------
//
package com.lizardtech.djvu{
	import flash.utils.ByteArray;
	
/**
 * This class represents 24 bit color image maps.
 *
 * @author Bill C. Riemers
 * @version $ $
 */
public class GPixmap
  extends GMap
{
  //~ Static fields/initializers ---------------------------------------------

  /** Used to represent division as multiplication. */
  protected static var invmap:Array= new Array(256);

  /** Indentity color correction table. */
  protected static var ctableI:Array= new Array(256);

  /** Cached color correction table. */
  protected static var ctable:Array= new Array(256);

  /**
   * The color correction subsample for the cached color table. 
   */
  protected static var lgamma:Number= -1;

  /** Used to quickly clip out of bounds values. */
  protected static var clip:Array= new Array(512); //Byte

  /** Used for attenuation */
  protected static var multiplierRefArray:Array=new Array(256);
  
  /**
   * Static initializers.
   */
  
  private function construction() {
    for(var i:int= 0; i < clip.length; i++)
    {
      clip[i] = int(((i < 256)
        ? i
        : 255));
    }
    for(var j:int= 1; j < invmap.length; j++)
    {
      invmap[j] = 0x10000/ j;
    }
    for(var k:int= 0; k < ctableI.length; k++)
    {
      ctableI[k] = k;
    }
    for(var l:int= 1; l < multiplierRefArray.length;)
    {
      multiplierRefArray[l++] = null;
    }
  }

  //~ Instance fields --------------------------------------------------------

  private var ref:GPixelReference= createGPixelReference(-1);

  //~ Constructors -----------------------------------------------------------

  /**
   * Creates a new GPixmap object.
   */
  public function GPixmap(bytes:Array=null,w:int=0,h:int=0) 
  {
  	  construction();
      super(3,2,1,0, false);
      if(bytes != null) {
      	init(bytes, w, h);
      }
  }

  //~ Methods ----------------------------------------------------------------

  /**
   * Creates an instance of GPixmap with the options interherited from the
   * specified reference.
   *
   * @param ref Object to interherit DjVuOptions from.
   *
   * @return a new instance of GPixmap.
   */
  public static function createGPixmap(ref:DjVuInterface):GPixmap{
    var options:DjVuOptions= ref.getDjVuOptions();

    return create(
      options,
      options.getGPixmapClass(),
      "com.lizardtech.djvu.GPixmap") as GPixmap;
  }

  /**
   * Fill the array with color correction constants.
   * 
   * @param gamma color correction subsample
   * 
   * @return the new color correction table
   */
  public static function getColorCorrection(
   	gamma:Number)
  {
    if((gamma < 0.1) || (gamma > 10))
    {
      trace("(GPixmap::color_correct) Illegal parameter");
    }
    var retval:Array;
    if((gamma < 1.0001) && (gamma > 0.999))
    {
      retval=ctableI;
    }
    else
    {
      if(gamma != lgamma)
      {
        ctable=new Array(256);//int[256];
        for(var i:int= 0; i < 256; i++)
        {
          var x:Number= i/255;

          if(DjVuOptions.BEZIERGAMMA)
          {
            var t:Number=
              (Math.sqrt(1.0+ (((gamma * gamma) - 1.0) * x)) - 1.0) / (gamma - 1.0);
            x = ((((1.0- gamma) * t) + (2* gamma)) * t) / (gamma + 1.0);
          }
          else
          {
            x = Math.pow(x, 1.0/ gamma);
          }

          ctable[i] = int(Math.floor((255 * x) + 0.5));
        }
        lgamma = gamma;
      }
      retval=ctable;
    }
    return retval;
  }
  
  /**
   * Creates or retrieves a cached multiplier array to use when attenuating.
   *
   * @return attenuation array
   */
  protected static function getMultiplier(maxgray:int):Array
  {
    var retval:Array=getFromReference(multiplierRefArray[maxgray]) as Array;
    if(retval == null)
    {
      retval = new int[maxgray];

      for(var i:int= 0; i < maxgray; i++)
      {
        retval[i] = 0x10000- ((i << 16) / maxgray);
      }
      multiplierRefArray[maxgray]=createSoftReference(retval,null);
    }
    return retval;
  }

  /**
   * Attenuate the specified bitmap.
   * 
   * @param bm Bitmap to attenuate
   * @param xpos horizontal position
   * @param ypos vertical position
   */
  public function attenuate(
    bm:GBitmap,
    xpos:int,
    ypos:int):void{
    // Check
    // Compute number of rows and columns
    var xrows:int= ypos + bm.rows();

    if(xrows > rows())
    {
      xrows = rows();
    }

    if(ypos > 0)
    {
      xrows -= ypos;
    }

    var xcolumns:int= xpos + bm.columns();

    if(xcolumns > columns())
    {
      xcolumns = columns();
    }

    if(xpos > 0)
    {
      xcolumns -= xpos;
    }

    if((xrows <= 0) || (xcolumns <= 0))
    {
      return;
    }

    // Precompute multiplier map
    var maxgray:int= bm.getGrays() - 1;
    var multiplier:Array= getMultiplier(maxgray);

    // Compute starting point
    var src:int= bm.rowOffset((ypos < 0)
        ? (-ypos)
        : 0) - ((xpos < 0)
      ? xpos
      : 0);
    var dst:int= rowOffset((ypos > 0)
        ? ypos
        : 0) + ((xpos > 0)
      ? xpos
      : 0);

    var dstPixel:GPixelReference= createGPixelReference(0);

    // Loop over rows
    for(var y:int= 0; y < xrows; y++)
    {
      // Loop over columns
      dstPixel.setOffset(dst);

      for(var x:int= 0; x < xcolumns; dstPixel.incOffset())
      {
        var srcpix:int= bm.getByteAt(src + (x++));

        // Perform pixel operation
        if(srcpix > 0)
        {
          if(srcpix >= maxgray)
          {
            dstPixel.setGray(0);
          }
          else
          {
            var level:int= multiplier[srcpix];
            dstPixel.setBGR(
              (dstPixel.getBlue() * level) >> 16,
              (dstPixel.getGreen() * level) >> 16,
              (dstPixel.getRed() * level) >> 16);
          }
        }
      }

      // Next line
      dst += getRowSize();
      src += bm.getRowSize();
    }
  }

  /**
   * Insert the specified bitmap with the specified color.
   * 
   * @param bm bitmap to insert
   * @param xpos horizontal position
   * @param ypos vertical position
   * @param color color to insert bitmap with
   */
  public function blit(
    bm:GBitmap,
    xpos:int,
    ypos:int,
    color:GPixel):void{
    // Check
    if(color == null)
    {
      return;
    }

    // Compute number of rows and columns
    var xrows:int= ypos + bm.rows();

    if(xrows > rows())
    {
      xrows = rows();
    }

    if(ypos > 0)
    {
      xrows -= ypos;
    }

    var xcolumns:int= xpos + bm.columns();

    if(xcolumns > columns())
    {
      xcolumns = columns();
    }

    if(xpos > 0)
    {
      xcolumns -= xpos;
    }

    if((xrows <= 0) || (xcolumns <= 0))
    {
      return;
    }

    // Precompute multiplier map
    var maxgray:int= bm.getGrays() - 1;
    var multiplier:Array= new int[maxgray];

    for(var i:int= 0; i < maxgray; i++)
    {
      multiplier[i] = 0x10000- ((i << 16) / maxgray);
    }

    // Cache target color
    var gr:int= color.getRed();
    var gg:int= color.getGreen();
    var gb:int= color.getBlue();

    // Compute starting point
    var src:int= bm.rowOffset((ypos < 0)
        ? (-ypos)
        : 0) - ((xpos < 0)
      ? xpos
      : 0);
    var dst:int= ((ypos > 0)
      ? rowOffset(ypos)
      : 0) + ((xpos > 0)
      ? xpos
      : 0);

    var dstPixel:GPixelReference= createGPixelReference(dst);

    // Loop over rows
    for(var y:int= 0; y < xrows; y++)
    {
      // Loop over columns
      dstPixel.setOffset(dst);

      for(var x:int= 0; x < xcolumns; dstPixel.incOffset())
      {
        var srcpix:int= bm.getByteAt(src + (x++));

        // Perform pixel operation
        if(srcpix != 0)
        {
          if(srcpix >= maxgray)
          {
            dstPixel.setBGR(gb, gg, gr);
          }
          else
          {
            var level0:int= multiplier[srcpix];
            var level1:int= 0x10000- level0;
            dstPixel.setBGR(
              clip[((dstPixel.getBlue() * level0) + (gb * level1)) >> 16],
              clip[((dstPixel.getGreen() * level0) + (gg * level1)) >> 16],
              clip[((dstPixel.getRed() * level0) + (gr * level1)) >> 16]);
          }
        }
      }

      // Next line
      dst += getRowSize();
      src += bm.getRowSize();
    }
  }

  /**
   * Correct the colors with a gamma subsample normalized to 1.0 for no correction.
   * 
   * @param gamma color correction
   */
  public final function applyGammaCorrection(gamma:Number):void{
    if(
      true
      || ((gamma > 0.999)
      && (gamma < 1.0009999999999999)))
    {
      return;
    }

    var gtable:Array=getColorCorrection(gamma);

    for(var i:int= 0; i < data.length; i++)
    {
      data[i] = int(gtable[data[i]]);
    }
  }

  /**
   * Fill this image from another source at reduced resolution.  Pixel 
   * averaging will be used.
   * 
   * @param src image map to reduce
   * @param subsample rate to subsample
   * @param pdr target bounds
   */
  public function downsample(
    src:GMap,
    subsample:int,
    pdr:GRect):void{
    var rect:GRect=
      new GRect(
        0,
        0,
        ((src.columns() + subsample) - 1) / subsample,
        ((src.rows() + subsample) - 1) / subsample);

    if(pdr != null)
    {
      if(
        (pdr.xmin < rect.xmin)
        || (pdr.ymin < rect.ymin)
        || (pdr.xmax > rect.xmax)
        || (pdr.ymax > rect.ymax))
      {
        throw new Error(
          "(GPixmap::downsample) Specified rectangle overflows destination pixmap");
      }
      rect = pdr;
    }

    this.init(
      rect.height(),
      rect.width(),
      null);

    var sy:int= rect.ymin * subsample;
    var sxz:int= rect.xmin * subsample;
    var sidx:int= src.rowOffset(sy);
    var didx:int= 0;

    var sptr:GPixelReference= src.createGPixelReference(0);
    var dptr:GPixelReference= createGPixelReference(0);

    for(var y:int= 0; y < rows(); y++)
    {
      var sx:int= sxz;

      for(var x:int= columns(); x-- > 0; dptr.incOffset())
      {
        var r:int= 0;
        var g:int= 0;
        var b:int= 0;
        var s:int= 0;
        var kidx:int= sidx;
        var lsy:int= sy + subsample;

        if(lsy > src.rows())
        {
          lsy = src.rows();
        }

        var lsx:int= sx + subsample;

        if(lsx > src.columns())
        {
          lsx = src.columns();
        }

        for(var rsy:int= sy; rsy < lsy; rsy++)
        {
          sptr.setOffset(kidx + sx);
          if(! isRampNeeded())
          {
            for(var rsx:int= lsx - sx; rsx-- > 0; sptr.incOffset())
            {
              r += sptr.getRed();
              g += sptr.getGreen();
              b += sptr.getBlue();
              s++;
            }
          }
          else
          {
            for(var rsx:int= lsx - sx; rsx-- > 0; sptr.incOffset())
            {
              var pix:GPixel=src.ramp(sptr);
              r += pix.getRed();
              g += pix.getGreen();
              b += pix.getBlue();
              s++;
            }
          }

          kidx += src.getRowSize();
        }

        if(s >= invmap.length)
        {
          dptr.setBGR(b / s, g / s, r / s);
        }
        else
        {
          dptr.setBGR(
            ((b * invmap[s]) + 32768) >> 16,
            ((g * invmap[s]) + 32768) >> 16,
            ((r * invmap[s]) + 32768) >> 16);
        }

        sx += subsample;
      }

      sy += subsample;
      sidx += src.rowOffset(subsample);
      dptr.setOffset(didx += getRowSize());
    }
  }

  /**
   * Fill this image from another source at reduced resolution of 4 vertical
   * pixels to 3.  An extrapulating pixel averaging algorithm is used. 
   * 
   * @param src image map to reduce
   * @param pdr target bounds
   *
   * @throws IllegalArgumentException if the target rectangle is out of bounds
   */
  public function downsample43(
    src:GMap,
    pdr:GRect):void{
    var srcwidth:int= src.columns();
    var srcheight:int= src.rows();
    var destwidth:int= int(Math.ceil(srcwidth*0.75));
    var destheight:int= int(Math.ceil(srcheight*0.75));
    var rect:GRect= new GRect(0, 0, destwidth, destheight);

    if(pdr != null)
    {
      if(
        (pdr.xmin < rect.xmin)
        || (pdr.ymin < rect.ymin)
        || (pdr.xmax > rect.xmax)
        || (pdr.ymax > rect.ymax))
      {
        throw new Error(
          "rectangle out of bounds" + "pdr=(" + pdr.xmin + "," + pdr.ymin
          + "," + pdr.xmax + "," + pdr.ymax + "),rect=(" + rect.xmin + ","
          + rect.ymin + "," + rect.xmax + "," + rect.ymax + ")");
      }

      rect         = pdr;
      destwidth    = rect.width();
      destheight   = rect.height();
    }

    this.init(destheight, destwidth, null);

    var sy:int= rect.ymin / 3;
    var dy:int= rect.ymin - (3* sy);

//    if(dy < 0)
//    {
//      sy--;
//      dy += 3;
//    }

    var sxz:int= rect.xmin / 3;
    var dxz:int= rect.xmin - (3* sxz);

    if(dxz < 0)
    {
      sxz--;
      dxz += 3;
    }

    sxz *= 4;
    sy *= 4;

    var spix0:GPixelReference= src.createGPixelReference(0);
    var spix1:GPixelReference= src.createGPixelReference(0);
    var spix2:GPixelReference= src.createGPixelReference(0);
    var spix3:GPixelReference= src.createGPixelReference(0);
    var dpix0:GPixelReference= createGPixelReference(0);
    var dpix1:GPixelReference= createGPixelReference(0);
    var dpix2:GPixelReference= createGPixelReference(0);
    while(dy < destheight)
    {
      spix0.setOffset(sy++, sxz);

      if(sy >= srcheight)
      {
        sy--;
      }

      spix1.setOffset(sy++, sxz);

      if(sy >= srcheight)
      {
        sy--;
      }

      spix2.setOffset(sy++, sxz);

      if(sy >= srcheight)
      {
        sy--;
      }

      spix3.setOffset(sy++, sxz);
      
      dpix0.setOffset((dy<0)?0:dy, dxz);

      if(++dy >= destheight)
      {
        dy--;
      }

      dpix1.setOffset((dy<0)?0:dy, dxz);

      if(++dy >= destheight)
      {
        dy--;
      }

      dpix2.setOffset(dy++, dxz);
      var dx:int= dxz;
      var sx:int= sxz;

      var pix0:GPixel=src.ramp(spix0);
      var pix1:GPixel=src.ramp(spix1);
      var pix2:GPixel=src.ramp(spix2);
      var pix3:GPixel=src.ramp(spix3);
      while(dx < destwidth)
      {
        var s00b:int= pix0.getBlue();
        var s00g:int= pix0.getGreen();
        var s00r:int= pix0.getRed();
        var s01b:int= pix1.getBlue();
        var s01g:int= pix1.getGreen();
        var s01r:int= pix1.getRed();
        var s02b:int= pix2.getBlue();
        var s02g:int= pix2.getGreen();
        var s02r:int= pix2.getRed();
        var s03b:int= pix3.getBlue();
        var s03g:int= pix3.getGreen();
        var s03r:int= pix3.getRed();

        if(++sx < srcwidth)
        {
          spix0.incOffset();
          spix1.incOffset();
          spix2.incOffset();
          spix3.incOffset();
          pix0=src.ramp(spix0);
          pix1=src.ramp(spix1);
          pix2=src.ramp(spix2);
          pix3=src.ramp(spix3);
        }

        var s10b:int= pix0.getBlue();
        var s10g:int= pix0.getGreen();
        var s10r:int= pix0.getRed();
        var s11b:int= pix1.getBlue();
        var s11g:int= pix1.getGreen();
        var s11r:int= pix1.getRed();
        var s12b:int= pix2.getBlue();
        var s12g:int= pix2.getGreen();
        var s12r:int= pix2.getRed();
        var s13b:int= pix3.getBlue();
        var s13g:int= pix3.getGreen();
        var s13r:int= pix3.getRed();

        if(++sx < srcwidth)
        {
          spix0.incOffset();
          spix1.incOffset();
          spix2.incOffset();
          spix3.incOffset();
          pix0=src.ramp(spix0);
          pix1=src.ramp(spix1);
          pix2=src.ramp(spix2);
          pix3=src.ramp(spix3);
        }

        var s20b:int= pix0.getBlue();
        var s20g:int= pix0.getGreen();
        var s20r:int= pix0.getRed();
        var s21b:int= pix1.getBlue();
        var s21g:int= pix1.getGreen();
        var s21r:int= pix1.getRed();
        var s22b:int= pix2.getBlue();
        var s22g:int= pix2.getGreen();
        var s22r:int= pix2.getRed();
        var s23b:int= pix3.getBlue();
        var s23g:int= pix3.getGreen();
        var s23r:int= pix3.getRed();

        if(++sx < srcwidth)
        {
          spix0.incOffset();
          spix1.incOffset();
          spix2.incOffset();
          spix3.incOffset();
          pix0=src.ramp(spix0);
          pix1=src.ramp(spix1);
          pix2=src.ramp(spix2);
          pix3=src.ramp(spix3);
        }

        var s30b:int= pix0.getBlue();
        var s30g:int= pix0.getGreen();
        var s30r:int= pix0.getRed();
        var s31b:int= pix1.getBlue();
        var s31g:int= pix1.getGreen();
        var s31r:int= pix1.getRed();
        var s32b:int= pix2.getBlue();
        var s32g:int= pix2.getGreen();
        var s32r:int= pix2.getRed();
        var s33b:int= pix3.getBlue();
        var s33g:int= pix3.getGreen();
        var s33r:int= pix3.getRed();

        if(++sx < srcwidth)
        {
          spix0.incOffset();
          spix1.incOffset();
          spix2.incOffset();
          spix3.incOffset();
          pix0=src.ramp(spix0);
          pix1=src.ramp(spix1);
          pix2=src.ramp(spix2);
          pix3=src.ramp(spix3);
        }

        dpix0.setBlue(((11*s00b)+(2*(s01b + s10b))+s11b+8) >> 4);
        dpix0.setGreen(((11*s00g)+(2*(s01g + s10g))+s11g+8) >> 4);
        dpix0.setRed(((11*s00r)+(2*(s01r + s10r))+s11r+8) >> 4);
        dpix1.setBlue(((7*(s01b+s02b))+s11b+s12b+8) >> 4);
        dpix1.setGreen(((7*(s01g+s02g))+s11g+s12g+8) >> 4);
        dpix1.setRed(((7*(s01r+s02r))+s11r+s12r+8) >> 4);
        dpix2.setBlue(((11*s03b)+(2*(s02b+s13b))+s12b+8) >> 4);
        dpix2.setGreen(((11*s03g)+(2*(s02g+s13g))+s12g+8) >> 4);
        dpix2.setRed(((11*s03r)+(2*(s02r+s13r))+s12r+8) >> 4);

        if(++dx < destwidth)
        {
          dpix0.incOffset();
          dpix1.incOffset();
          dpix2.incOffset();
        }

        dpix0.setBlue(((7*(s10b + s20b)) + s11b + s21b + 8) >> 4);
        dpix0.setGreen(((7*(s10g + s20g)) + s11g + s21g + 8) >> 4);
        dpix0.setRed(((7*(s10r + s20r)) + s11r + s21r + 8) >> 4);
        dpix1.setBlue((s12b + s22b + s11b + s21b + 2) >> 2);
        dpix1.setGreen((s12g + s22g + s11g + s21g + 2) >> 2);
        dpix1.setRed((s12r + s22r + s11r + s21r + 2) >> 2);
        dpix2.setBlue(((7* (s13b + s23b)) + s12b + s22b + 8) >> 4);
        dpix2.setGreen(((7* (s13g + s23g)) + s12g + s22g + 8) >> 4);
        dpix2.setRed(((7* (s13r + s23r)) + s12r + s22r + 8) >> 4);

        if(++dx < destwidth)
        {
          dpix0.incOffset();
          dpix1.incOffset();
          dpix2.incOffset();
        }

        dpix0.setBlue(((11* s30b) + (2* (s31b + s20b)) + s21b + 8) >> 4);
        dpix0.setGreen(((11* s30g) + (2* (s31g + s20g)) + s21g + 8) >> 4);
        dpix0.setRed(((11* s30r) + (2* (s31r + s20r)) + s21r + 8) >> 4);
        dpix1.setBlue(((7* (s31b + s32b)) + s21b + s22b + 8) >> 4);
        dpix1.setGreen(((7* (s31g + s32g)) + s21g + s22g + 8) >> 4);
        dpix1.setRed(((7* (s31r + s32r)) + s21r + s22r + 8) >> 4);
        dpix2.setBlue(((11* s33b) + (2* (s32b + s23b)) + s22b + 8) >> 4);
        dpix2.setGreen(((11* s33g) + (2* (s32g + s23g)) + s22g + 8) >> 4);
        dpix2.setRed(((11* s33r) + (2* (s32r + s23r)) + s22r + 8) >> 4);

        if(++dx < destwidth)
        {
          dpix0.incOffset();
          dpix1.incOffset();
          dpix2.incOffset();
        }
      }
    }
  }

  /**
   * Insert the reference map at the specified location.
   *
   * @param ref map to insert
   * @param dx horizontal position to insert at
   * @param dy vertical position to insert at
   */
  public override function fill(
    ref:*, //GMap
    dx:int=0,
    dy:int=0):void{
    var x0:int= (dx > 0)
      ? dx
      : 0;
    var y0:int= (dy > 0)
      ? dy
      : 0;
    var x1:int= (dx < 0)
      ? (-dx)
      : 0;
    var y1:int= (dy < 0)
      ? (-dy)
      : 0;
    var w0:int= columns() - x0;
    var w1:int= ref.columns() - x1;
    var w:int= (w0 < w1)
      ? w0
      : w1;
    var h0:int= rows() - y0;
    var h1:int= ref.rows() - y1;
    var h:int= (h0 < h1)
      ? h0
      : h1;

    if((w > 0) && (h > 0))
    {
      var pixel:GPixelReference= createGPixelReference(0);
      var refPixel:GPixelReference= ref.createGPixelReference(0);

      do
      {
        pixel.setOffset(y0++, x0);
        refPixel.setOffset(y1++, x1);


        if(! isRampNeeded())
        {
          pixel.setPixels(refPixel,w);
        }
        else
        {
          var i:int= w;
          do
          {
            pixel.set(ref.ramp(refPixel));
            pixel.incOffset();
            refPixel.incOffset();
          }
          while(--i > 0);
        }
      }
      while(--h > 0);
    }
  }

// as3 override mappings
  public function init(a:*, b:*=null, c:*=null):GPixmap {
  	  if(a is Array) {
  	  	return initA(a as Array, b as int, c as int) as GPixmap;
  	  } else if(a is int) {
	  	return initB(a as int, b as int, c as GPixel) as GPixmap;
	  } else if (a is GMap) {
	  		if(b != null) {
	  			initC(a as GMap, b as GRect) as GPixmap;
	  		} else {
	  			initD(a as GMap) as GPixmap;
	  		}
	  }
	  return null;
  }

  /**
   * Initiallize this pixmap with a preallocated buffer.
   *
   * @param data buffer to use
   * @param arows number of rows
   * @param acolumns number of columns
   *
   * @return the initialized pixmap
   */
  function initA(
    data:Array,
    arows:int,
    acolumns:int):GPixmap{
    nrows       = arows;
    ncolumns    = acolumns;
    this.data   = data;

    return this;
  }  

  /**
   * Initialize this pixmap to the specified size and fill in the specified color.
   *
   * @param arows number of rows
   * @param acolumns number of columns
   * @param filler fill color
   *
   * @return the initialized pixmap
   */
  public function initB(
    arows:int,
    acolumns:int,
    filler:GPixel):GPixmap{
//    boolean needFill=false;
    if((arows != nrows) || (acolumns != ncolumns))
    {
      data   = null;
      nrows       = arows;
      ncolumns    = acolumns;
    }

    var npix:int= rowOffset(rows());

    if(npix > 0)
    {
      if(data == null)
      {
        data = new Array(npix * 3);
//        needFill=true;
      }

      if(filler != null)
      {
        var b:uint= filler.blueByte();
        var g:uint= filler.greenByte();
        var r:uint= filler.redByte();

        for(var i:int= 0; i < data.length;)
        {
          data[i++]   = b;
          data[i++]   = g;
          data[i++]   = r;
        }
      }
//      else if(needFill)
//      {
//        for(int i = 0; i < data.length;)
//        {
//          data[i++] = -51;
//        }
//      }
    }

    return this;
  }

  /**
   * Initialize this pixmap from a segment of another image map.
   *
   * @param ref image map to initialize from
   * @param rect bounding rectangle to initialize from
   *
   * @return the initialized pixmap
   */
  public function initC(
    ref:GMap,
    rect:GRect):GPixmap{
    this.init(
      rect.height(),
      rect.width(),
      (GPixel((null))));

    var rect2:GRect= new GRect(0, 0,
        ref.columns(),
        ref.rows());
    rect2.intersect(rect2, rect);
    rect2.translate(-rect.xmin, -rect.ymin);

    if(!rect2.isEmpty())
    {
      var pixel:GPixelReference= createGPixelReference(0);
      var refPixel:GPixelReference=ref.createGPixelReference(0);

      for(var y:int= rect2.ymin; y < rect2.ymax; y++)
      {
        pixel.setOffset(y, rect2.xmin);
        refPixel.setOffset(y + rect.ymin, rect.xmin + rect2.xmin);

        if(! isRampNeeded())
        {
          for(
            var x:int= rect2.xmax - rect2.xmin;
            x-- > 0;
            pixel.incOffset(), refPixel.incOffset())
          {
            pixel.set(refPixel);
          }
        }
        else
        {
          for(
            var x:int= rect2.xmax - rect2.xmin;
            x-- > 0;
            pixel.incOffset(), refPixel.incOffset())
          {
            pixel.set(ref.ramp(refPixel));
          }
        }
      }
    }

    return this;
  }

  /**
   * Initialize this pixmap from another image map.
   *
   * @param ref image map to initialize from
   *
   * @return the initialized pixmap
   */
  public function initD(ref:GMap):GPixmap{
    this.init(
      ref.rows(),
      ref.columns(),
      (GPixel((null))));

    var pixel:GPixelReference= createGPixelReference(0);

    if((rows() > 0) && (columns() > 0))
    {
      var refPixel:GPixelReference=
        (GPixmap(ref)).createGPixelReference(0);

      for(var y:int= 0; y < rows(); y++)
      {
        pixel.setOffset(y, 0);
        refPixel.setOffset(y, 0);

        if(! isRampNeeded())
        {
          for(var x:int= columns(); x-- > 0;
            pixel.incOffset(), refPixel.incOffset())
          {
            pixel.set(refPixel);
          }
        }
        else
        {
          for(var x:int= columns(); x-- > 0;
            pixel.incOffset(), refPixel.incOffset())
          {
            pixel.set(ref.ramp(refPixel));
          }
        }
      }
    }

    return this;
  }


  /**
   * Draw the foreground layer onto this background image.
   * 
   * @param mask the mask layer
   * @param foregroundMap the foreground colors
   * @param supersample rate to upsample the foreground colors
   * @param subsample rate to subsample the foreground colors
   * @param bounds the target rectangle
   * @param gamma color correction factor
   * 
   * @throws IllegalArgumentException if the specified bounds are not contained in the page
   */
  public function stencil(
    mask:GBitmap,
    foregroundMap:GPixmap,
    supersample:int,
    subsample:int,
    bounds:GRect,
    gamma:Number):void{
    // Check arguments
    var rect:GRect= new GRect(0, 0, (foregroundMap.columns() * supersample+subsample-1)/subsample, (foregroundMap.rows() * supersample+subsample-1)/subsample);

    if(bounds != null)
    {
      if(
        (bounds.xmin < rect.xmin)
        || (bounds.ymin < rect.ymin)
        || (bounds.xmax > rect.xmax)
        || (bounds.ymax > rect.ymax))
      {
        throw new Error(
          "rectangle out of bounds" + "bounds=(" + bounds.xmin + "," + bounds.ymin
          + "," + bounds.xmax + "," + bounds.ymax + "),rect=(" + rect.xmin + ","
          + rect.ymin + "," + rect.xmax + "," + rect.ymax + ")");
      }

      rect = bounds;
    }

    // Compute number of rows
    var xrows:int= rows();

    if(int(mask.rows() )< xrows)
    {
      xrows = mask.rows();
    }

    if(rect.height() < xrows)
    {
      xrows = rect.height();
    }

    // Compute number of columns
    var xcolumns:int= columns();

    if(int(mask.columns() )< xcolumns)
    {
      xcolumns = mask.columns();
    }

    if(rect.width() < xcolumns)
    {
      xcolumns = rect.width();
    }

    // Precompute multiplier map
    var maxgray:int= mask.getGrays() - 1;
    var multiplier:Array= new int[maxgray];

    for(var i:int= 1; i < maxgray; i++)
    {
      multiplier[i] = (0x10000* i) / maxgray;
    }

    // Prepare color correction table
    var gtable:Array=getColorCorrection(gamma);

    var ratioFg:Number=Number(supersample)/Number(subsample);
    // Compute starting point in blown up foreground pixmap
    var fgy:int= (rect.ymin * subsample )/ supersample;
    var fgy1:Number= rect.ymin - ratioFg*fgy;

    if(fgy1 < 0)
    {
      fgy--;
      fgy1 += ratioFg;
    }

    var fgxz:int= (rect.xmin*subsample)/ supersample;
    var fgx1z:Number= rect.xmin - ratioFg*fgxz;

    if(fgx1z < 0)
    {
      fgxz--;
      fgx1z += ratioFg;
    }

    var fg:int= foregroundMap.rowOffset(fgy);
    var fgx:GPixelReference= foregroundMap.createGPixelReference(0);
    var dst:GPixelReference= createGPixelReference(0);

    // Loop over rows
    for(var y:int= 0; y < xrows; y++)
    {
      // Loop over columns
      fgx.setOffset(fg + fgxz);

      var fgx1:Number= fgx1z;
      dst.setOffset(y, 0);

      var src:int= mask.rowOffset(y);

      for(var x:int= 0; x < xcolumns; x++, dst.incOffset())
      {
        var srcpix:int= mask.getByteAt(src + x);

        // Perform pixel operation
        if(srcpix > 0)
        {
          if(srcpix >= maxgray)
          {
            dst.setBGR(
              gtable[fgx.getBlue()],
              gtable[fgx.getGreen()],
              gtable[fgx.getRed()]);
          }
          else
          {
            var level:int= multiplier[srcpix];
            dst.setBGR(
              ((dst.getBlue() * (0x10000- level))
              + (level * int(gtable[fgx.getBlue()]))) >> 16,
              ((dst.getGreen() * (0x10000- level))
              + (level * int(gtable[fgx.getGreen()]))) >> 16,
              ((dst.getRed() * (0x10000- level))
              + (level * int(gtable[fgx.getRed()]))) >> 16);
          }
        }

        // Next column
        if(++fgx1 >= ratioFg)
        {
          fgx1 -= ratioFg;
          fgx.incOffset();
        }
      }

      // Next line
      if(++fgy1 >= ratioFg)
      {
        fgy1 -= ratioFg;
        fg += foregroundMap.getRowSize();
      }
    }
  }


  /**
   * Copy this image with a translated origin.
   *
   * @param dx horizontal distance to translate
   * @param dy vertical distance to translate
   * @param retval an old image to try and reuse for the return value
   *
   * @return the translated image
   */
  public override function translate(
    dx:int,
    dy:int,
    retval:GMap):GMap{
    if(
      !(retval instanceof GPixmap)
      || (retval.columns() != columns())
      || (retval.rows() != rows()))
    {
      retval = createGPixmap(this).init(
          rows(),
          columns(),
          null);
    }

    retval.fill(this, -dx, -dy);

    return retval;
  }  
}}